﻿
using System.Collections;
using System.Collections.Generic;
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace ShevaEngine.Core.Modules.Graphics
{
    /// <summary>
    /// Interface draw call.
    /// </summary>
    public abstract class DrawCall
    {
        /// <summary>Allow hw instancing.</summary>
        public Boolean AllowHWInstancing { get; internal set; }

        /// <summary>InstancesVBO.</summary>
        private static DynamicVertexBuffer WorldMatricesVertexBuffer;
        /// <summary>InstancesVBO.</summary>
        private static DynamicVertexBuffer WorldViewITMatricesVertexBuffer;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="drawCallKey"></param>
        public DrawCall()
        {
            this.AllowHWInstancing = false;
        }


        /// <summary>
        /// Constructor.
        /// </summary>
        static DrawCall()
        {
            DrawCall.WorldMatricesVertexBuffer = new DynamicVertexBuffer(ShevaEngine.Instance.GraphicsDevice,
                        InstanceVertexDeclaration.BlendWeight, 128, BufferUsage.WriteOnly);
            DrawCall.WorldViewITMatricesVertexBuffer = new DynamicVertexBuffer(ShevaEngine.Instance.GraphicsDevice,
                        InstanceVertexDeclaration.Colors, 128, BufferUsage.WriteOnly);
        }

        /// <summary>Render.</summary>
        public abstract void Render();

        /// <summary>Render HW instancing.</summary>
        public abstract void RenderHWInstancing(int instancesCount);
        

        /// <summary>
        /// Method renders draw call.
        /// </summary>
        /// <param name="drawCall"></param>
        /// <param name="worldMatrices"></param>
        public static void RenderDrawCalls(Effect effect, DrawCall drawCall, Matrix[] matrices)
        {
            bool hwInstanced = false;
            string techniqueName = effect.CurrentTechnique.Name;

            int matricesCount = matrices.Length;

            if (matricesCount > 50 && drawCall.AllowHWInstancing)
            {
                if (DrawCall.WorldMatricesVertexBuffer.VertexCount < matricesCount)
                    DrawCall.WorldMatricesVertexBuffer = new DynamicVertexBuffer(ShevaEngine.Instance.GraphicsDevice,
                        InstanceVertexDeclaration.BlendWeight, matricesCount, BufferUsage.WriteOnly);

                // Transfer the latest instance transform matrices into the instanceVertexBuffer.
                DrawCall.WorldMatricesVertexBuffer.SetData(matrices, 0, matricesCount, SetDataOptions.Discard);

                ShevaEngine.Instance.GraphicsDevice.SetVertexBuffers(
                    ShevaEngine.Instance.GraphicsDevice.GetVertexBuffers()[0],
                    new VertexBufferBinding(DrawCall.WorldMatricesVertexBuffer, 0, 1));

                if (!hwInstanced)
                {
                    effect.CurrentTechnique = effect.Techniques[techniqueName + "HwInstancing"];
                    effect.CurrentTechnique.Passes[0].Apply();

                    hwInstanced = true;
                }

                drawCall.RenderHWInstancing(matricesCount);

                ShevaEngine.Instance.GraphicsDevice.SetVertexBuffer(
                    ShevaEngine.Instance.GraphicsDevice.GetVertexBuffers()[0].VertexBuffer);
            }
            else
            {
                if (hwInstanced)
                {
                    effect.CurrentTechnique = effect.Techniques[techniqueName];
                    hwInstanced = false;
                }

                for (int i = matricesCount - 1; i >= 0; i--)
                {
                    effect.Parameters["World"].SetValue(matrices[i]);                    
                    effect.CurrentTechnique.Passes[0].Apply();

                    drawCall.Render();
                }
            }

            if (hwInstanced)
                effect.CurrentTechnique = effect.Techniques[techniqueName];
        }  

        /// <summary>
        /// Method renders draw call.
        /// </summary>
        /// <param name="drawCall"></param>
        /// <param name="worldMatrices"></param>
        public static void RenderDrawCalls(Effect effect, DrawCall drawCall, Matrix[] worldMatrices, Matrix[] worldViewITMatrices)
        {
            bool hwInstanced = false;
            string techniqueName = effect.CurrentTechnique.Name;

            int matricesCount = worldMatrices.Length;

            if (matricesCount > 50 && drawCall.AllowHWInstancing)
            {
                if (DrawCall.WorldMatricesVertexBuffer.VertexCount < matricesCount)
                {
                    DrawCall.WorldMatricesVertexBuffer = new DynamicVertexBuffer(ShevaEngine.Instance.GraphicsDevice,
                        InstanceVertexDeclaration.BlendWeight, matricesCount, BufferUsage.WriteOnly);

                    DrawCall.WorldViewITMatricesVertexBuffer = new DynamicVertexBuffer(ShevaEngine.Instance.GraphicsDevice,
                        InstanceVertexDeclaration.Colors, matricesCount, BufferUsage.WriteOnly);
                }

                // Transfer the latest instance transform matrices into the instanceVertexBuffer.
                DrawCall.WorldMatricesVertexBuffer.SetData(worldMatrices, 0, matricesCount, SetDataOptions.Discard);
                DrawCall.WorldViewITMatricesVertexBuffer.SetData(worldViewITMatrices, 0, matricesCount, SetDataOptions.Discard);

                ShevaEngine.Instance.GraphicsDevice.SetVertexBuffers(
                    ShevaEngine.Instance.GraphicsDevice.GetVertexBuffers()[0],
                    new VertexBufferBinding(DrawCall.WorldMatricesVertexBuffer, 0, 1),
                    new VertexBufferBinding(DrawCall.WorldViewITMatricesVertexBuffer, 0, 1));

                if (!hwInstanced)
                {
                    effect.CurrentTechnique = effect.Techniques[techniqueName + "HwInstancing"];
                    effect.CurrentTechnique.Passes[0].Apply();

                    hwInstanced = true;
                }

                drawCall.RenderHWInstancing(matricesCount);

                ShevaEngine.Instance.GraphicsDevice.SetVertexBuffer(
                    ShevaEngine.Instance.GraphicsDevice.GetVertexBuffers()[0].VertexBuffer);
            }
            else
            {
                if (hwInstanced)
                {
                    effect.CurrentTechnique = effect.Techniques[techniqueName];
                    hwInstanced = false;
                }

                for (int i = matricesCount - 1; i >= 0; i--)
                {
                    Matrix actualMatrix = worldMatrices[i];

                    effect.Parameters["WorldViewIT"].SetValue(worldViewITMatrices[i]);
                    effect.Parameters["World"].SetValue(actualMatrix);

                    effect.CurrentTechnique.Passes[0].Apply();

                    drawCall.Render();
                }
            }

            if (hwInstanced)
                effect.CurrentTechnique = effect.Techniques[techniqueName];
        }
    }    
}
